package org.abstractmeta.commandify.generator.builder.handler;

import org.abstractmeta.code.g.code.*;
import org.abstractmeta.code.g.code.handler.ConstructorHandler;
import org.abstractmeta.code.g.core.annotation.SuppressWarningsBuilder;
import org.abstractmeta.code.g.core.code.builder.JavaConstructorBuilder;
import org.abstractmeta.code.g.core.code.builder.JavaMethodBuilder;
import org.abstractmeta.code.g.core.code.builder.JavaTypeBuilderImpl;
import org.abstractmeta.code.g.core.internal.ParameterizedTypeImpl;
import org.abstractmeta.code.g.core.util.CodeGeneratorUtil;
import org.abstractmeta.code.g.core.util.ReflectUtil;
import org.abstractmeta.code.g.generator.Context;
import org.abstractmeta.commandify.core.command.AbstractConstructorCommand;
import org.abstractmeta.commandify.generator.util.CommandifyGeneratorUtil;
import org.abstractmeta.reflectify.ReflectifyRegistry;
import org.abstractmeta.reflectify.generator.ReflectifyRegistryProvider;

import java.lang.reflect.Type;
import java.util.List;

/**
 * ConstructorHandler to add CommandifyConstructorCommand
 *
 * @author Adrian Witas
 */
public class CommandifyConstructor implements ConstructorHandler {

    @Override
    public void handle(JavaTypeBuilder owner, JavaConstructor target, Context context) {
        ClassLoader classLoader = context.getOptional(ClassLoader.class);
        Class compiledType = loadClass(owner.getSourceType(), classLoader);

        ReflectifyRegistryProvider reflectifyRegistryProvider = new ReflectifyRegistryProvider(compiledType);
        owner.addClassPathDependency(reflectifyRegistryProvider.getClassPathEntries());
        ReflectifyRegistry reflectifyRegistry = reflectifyRegistryProvider.get();

        Class reflectifyClass = reflectifyRegistry.get(compiledType).getClass();

        classLoader = reflectifyClass.getClassLoader();
        context.replace(ClassLoader.class, classLoader);

        owner.getImporter().addTypes(reflectifyClass);
        Type reflectifyType = new ParameterizedTypeImpl(null, reflectifyClass);
        target.getBodyLines().add(String.format("getCommands().setReflectify(new %s());", owner.getImporter().getSimpleTypeName(reflectifyType)));


        JavaTypeBuilder constructorCommandify = buildCommandifyConstructor(owner, target, context);
        constructorCommandify.addModifiers(JavaModifier.PUBLIC, JavaModifier.STATIC);
        StringBuilder constructorInitLiteral = new StringBuilder();
        JavaType constructorCommandifyType = constructorCommandify.build();

        owner.addNestedJavaTypes(constructorCommandifyType);
        String simpleName = constructorCommandify.getSimpleName();
        for (JavaParameter parameter : target.getParameters()) {
            if (constructorInitLiteral.length() > 0) constructorInitLiteral.append(",");
            constructorInitLiteral.append(parameter.getName());
        }
        target.getBodyLines().add(String.format("getCommands().setConstructorCommand(new %s(%s));", simpleName, constructorInitLiteral));
        CodeGeneratorUtil.addUncheckedWarningAnnotation(target.getAnnotations());

    }

    //TODO move to utils
    protected Class loadClass(JavaType compliedType, ClassLoader classLoader) {
        if (classLoader == null) {
            classLoader = this.getClass().getClassLoader();
        }
        try {
            return classLoader.loadClass(compliedType.getName());
        } catch (Exception e) {
            throw new IllegalStateException("Type " + compliedType.getName() + " has not been found or compiled");
        }
    }


    protected JavaTypeBuilder buildCommandifyConstructor(JavaTypeBuilder owner, JavaConstructor target, Context context) {
        Type superType = CommandifyGeneratorUtil.getSuperType(owner);
        String typeName = CodeGeneratorUtil.getClassName(owner.getSimpleName(), "ConstructorCommand");
        JavaTypeBuilder result = new JavaTypeBuilderImpl(typeName);
        result.setNested(true);
        Type commandifyConstrictorSuperType = new ParameterizedTypeImpl(null, AbstractConstructorCommand.class, superType);
        result.setSuperType(commandifyConstrictorSuperType);
        result.addConstructor(buildConstructor(owner, target, context, result));

        JavaMethod executeMethod = buildExecuteMethod(owner, target, context, superType);
        result.addMethod(executeMethod);

        return result;
    }

    protected JavaMethod buildExecuteMethod(JavaTypeBuilder owner, JavaConstructor target, Context context, Type superType) {
        JavaMethodBuilder executeMethod = new JavaMethodBuilder();
        executeMethod.setResultType(superType).addModifiers(JavaModifier.PUBLIC);
        executeMethod.setName("execute");
        executeMethod.addAnnotation(new SuppressWarningsBuilder().addValue("unchecked").build());
        StringBuilder constructorArguments = new StringBuilder();
        List<JavaParameter> parameters = target.getParameters();

        for (int i = 0; i < parameters.size(); i++) {
            JavaParameter javaParameter = parameters.get(i);
            String javaParameterType = ReflectUtil.getRawClass(javaParameter.getType()).getSimpleName();
            if (constructorArguments.length() > 0) constructorArguments.append(",");
            constructorArguments.append(String.format("(%s)getValues()[%s]", javaParameterType, i));
        }
        executeMethod.addBodyLines(String.format("return new %s(%s);", owner.getSimpleName(), constructorArguments));
        return executeMethod.build();
    }

    protected JavaConstructor buildConstructor(JavaTypeBuilder owner, JavaConstructor target, Context context, JavaTypeBuilder constructorCommandify) {
        JavaConstructorBuilder constructorBuilder = new JavaConstructorBuilder();
        constructorBuilder.setName(constructorCommandify.getSimpleName()).addModifiers(JavaModifier.PUBLIC);
        StringBuilder constructorParameterTypes = new StringBuilder();
        StringBuilder constructorParameterName = new StringBuilder();


        for (JavaParameter javaParameter : target.getParameters()) {
            constructorBuilder.addParameters(javaParameter);
            if (constructorParameterName.length() > 0) constructorParameterName.append(",");
            constructorParameterName.append(javaParameter.getName());
            String javaParameterType = ReflectUtil.getRawClass(javaParameter.getType()).getSimpleName();
            constructorParameterTypes.append(javaParameterType).append(",");
        }
        constructorBuilder.addBodyLines(String.format("super(%s%s);", constructorParameterTypes, constructorParameterName));
        return constructorBuilder.build();
    }
}
